/*
 * Created on Dec 29, 2003
 *
 */
package com.unash.z3950.pdu;


/**
 * @author Ashwini Gokhale
 */

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;

import com.unash.asn.AsnTags;
import com.unash.asn.BerEncoder;
import com.unash.util.AsnUtil;

public class Z3950BerEncoder implements Z3950Tags, AsnTags, Z3950Encoder{
	
	public byte[] encode(InitResponse initRes)
	throws Exception{
		byte[] value = null;
		ByteArrayOutputStream result = new ByteArrayOutputStream();
		
		//reference id
		if(initRes.getReferenceId() != null)
		{
			result.write(AsnUtil.encodeTag(REFERENCE_ID,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeOctetString(initRes.getReferenceId());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//protocol version
		result.write(AsnUtil.encodeTag(PROTOCOL_VERSION,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeBitString(initRes.getProtocolVersion());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//options
		result.write(AsnUtil.encodeTag(OPTIONS,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeBitString(initRes.getOptions());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//preferred message size
		result.write(AsnUtil.encodeTag(PREFERRED_MESSAGE_SIZE,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(initRes.getPreferredMessageSize());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//exceptional record size
		result.write(AsnUtil.encodeTag(EXCEPTIONAL_RECORD_SIZE,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(initRes.getExceptionalRecordSize());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//result
		result.write(AsnUtil.encodeTag(RESULT,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeBoolean(initRes.getResult());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//Implementation ID
		if(initRes.getImplementationId() != null){
			result.write(AsnUtil.encodeTag(IMPLEMENTATION_ID,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeVisibleString(initRes.getImplementationId());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//Implementation Name
		if(initRes.getImplementationName() != null){
			result.write(AsnUtil.encodeTag(IMPLEMENTATION_NAME,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeVisibleString(initRes.getImplementationName());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//Implementation Version
		if(initRes.getImplementationVersion() != null){
			result.write(AsnUtil.encodeTag(IMPLEMENTATION_VERSION,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeVisibleString(initRes.getImplementationVersion());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		bos.write(AsnUtil.encodeTag(INIT_RESPONSE,CONTEXT_SPECIFIC,false));
		bos.write(AsnUtil.encodeLength(result.size()));
		bos.write(result.toByteArray());
		return bos.toByteArray();
		
	}//encodeRequest(OutputStream bs)
	
	
	public byte[] encode(SearchResponse searchRes)
	throws Exception{
		byte[] value;
		ByteArrayOutputStream result = new ByteArrayOutputStream();
		
		//ReferenceID
		if(searchRes.getReferenceId() != null)
		{
			result.write(AsnUtil.encodeTag(REFERENCE_ID,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeOctetString(searchRes.getReferenceId());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//Result Count	
		result.write(AsnUtil.encodeTag(RESULT_COUNT,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(searchRes.getResultCount());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//Number Of records returned.
		result.write(AsnUtil.encodeTag(NUMBER_OF_RECORDS_RETURNED,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(searchRes.getNumberOfRecordsReturned());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//Next result set position
		result.write(AsnUtil.encodeTag(NEXT_RESULT_SET_POSITION,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(searchRes.getNextResultSetPosition());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);

		//Search status
		result.write(AsnUtil.encodeTag(SEARCH_STATUS,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeBoolean(searchRes.getSearchStatus());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		//Result set status
		if(searchRes.getResultSetStatus() != null){
			result.write(AsnUtil.encodeTag(RESULT_SET_STATUS,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeInteger(searchRes.getResultSetStatus());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//Present status
		if(searchRes.getPresentStatus() != null){
			result.write(AsnUtil.encodeTag(PRESENT_STATUS,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeInteger(searchRes.getPresentStatus());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//records
		//result.write(encodeRecords(searchRes));
		if(searchRes.getRecords() != null)
			encodeRecords(searchRes.getRecords(),result);
		
		//TODO: AdditionalSearchInfo
		//TODO:OtherInfo
				
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		bos.write(AsnUtil.encodeTag(SEARCH_RESPONSE,CONTEXT_SPECIFIC,false));
		bos.write(AsnUtil.encodeLength(result.size()));
		bos.write(result.toByteArray());
		return bos.toByteArray();

	}//encode
	
	public byte[] encode(PresentResponse presentRes)
	throws Exception{
		byte[] value = null;
		ByteArrayOutputStream result = new ByteArrayOutputStream();
		
		result.write(AsnUtil.encodeTag(REFERENCE_ID,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeOctetString(presentRes.getReferenceId());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		result.write(AsnUtil.encodeTag(NUMBER_OF_RECORDS_RETURNED,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(presentRes.getNumberOfRecordsReturned());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		result.write(AsnUtil.encodeTag(NEXT_RESULT_SET_POSITION,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(presentRes.getNextResultSetPosition());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);

		result.write(AsnUtil.encodeTag(PRESENT_STATUS,CONTEXT_SPECIFIC,false));
		value = BerEncoder.encodeInteger(presentRes.getPresentStatus());
		result.write(AsnUtil.encodeLength(value.length));
		result.write(value);
		
		encodeRecords(presentRes.getRecords(),result);
		
		//otherInformation
		
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		bs.write(AsnUtil.encodeTag(PRESENT_RESPONSE,CONTEXT_SPECIFIC,false));
		bs.write(AsnUtil.encodeLength(result.size()));
		bs.write(result.toByteArray());
		
		
		return bs.toByteArray(); 
	}//encode
	
	
	public void encodeRecords(Records records,ByteArrayOutputStream result)
	throws UnsupportedEncodingException,Exception{
		byte[] value = null;
		//ResponseRecords
		if(records instanceof ResponseRecords){
			
			ResponseRecords responseRecords = (ResponseRecords)records;
			result.write(AsnUtil.encodeTag(RESPONSE_RECORDS,CONTEXT_SPECIFIC,false));
			value = encodeSequenceOfNamePlusRecord(responseRecords); 
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);
		}//if
		
		//NonSurrogateDiagnostic
		if(records instanceof NonSurrogateDiagnostic){
			NonSurrogateDiagnostic nonSurrogateDiagnostic = (NonSurrogateDiagnostic)records;
			result.write(AsnUtil.encodeTag(NON_SURROGATE_DIAGNOSTIC,CONTEXT_SPECIFIC,false));
			value = encodeSequenceDefaultDiagFormat(nonSurrogateDiagnostic.getNonSurrogateDiagnostic());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value); 
		}//if
		
		//MultipleNonSurDiagnostic
		if(records instanceof MultipleNonSurDiagnostic){
			MultipleNonSurDiagnostic multipleNonSurDiagnostic = (MultipleNonSurDiagnostic)records;
			result.write(AsnUtil.encodeTag(MULTIPLE_NON_SUR_DIAGNOSTIC,CONTEXT_SPECIFIC,false));
			value = encodeSequenceOfDiagRec(multipleNonSurDiagnostic.getMultipleNonSurDiagnostic());
			result.write(value.length);
			result.write(value); 
		}//if
		
		
	}//encodeRecords
	
	
	public byte[] encodeSequenceOfDiagRec(DiagRec[] diagRec)
	throws Exception{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		for(int i=0;i<diagRec.length;i++){
			bs.write(encodeChoiceDiagRec(diagRec[i]));	
		}//for
		return bs.toByteArray();
	}//encodeSequenceOfDiagRec
	
	
	public byte[] encodeSequenceOfNamePlusRecord(ResponseRecords respRecs)
	throws IllegalArgumentException,UnsupportedEncodingException,Exception{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		for(int i=0;i<respRecs.getResponseRecords().length;i++){
			encodeSequenceNamePlusRecord(respRecs.getResponseRecords()[i],bs);	
		}//for
		return bs.toByteArray();
		
	}//encodeSequenceOfNamePlusRecord
	
	
	public void encodeSequenceNamePlusRecord(NamePlusRecord namePlusRecord,ByteArrayOutputStream result)
	throws UnsupportedEncodingException,Exception{
		byte[] value;
		
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		
		//Name
		if(namePlusRecord.getName() != null){
			bs.write(AsnUtil.encodeTag(NAME,CONTEXT_SPECIFIC,false));
			value = BerEncoder.encodeVisibleString(namePlusRecord.getName());
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		bs.write(AsnUtil.encodeTag(RECORD,CONTEXT_SPECIFIC,false));
		value = encodeChoiceRecord(namePlusRecord.getRecord());
		bs.write(AsnUtil.encodeLength(value.length));
		bs.write(value);
		
		result.write(AsnUtil.encodeTag(SEQUENCE,UNIVERSAL,false));
		result.write(AsnUtil.encodeLength(bs.size()));
		result.write(bs.toByteArray());
		
				
	}//encodeNamePlusRecord
	
	
	public byte[] encodeChoiceRecord(Record record)
	throws Exception{
		byte[] value = null;
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		//Retrieval Record
		if(record instanceof RetrievalRecord){
			//RetrievalRecord retrievalRecord = record;
			
			//TODO:retrievalrecord :External
			bs.write(AsnUtil.encodeTag(RETRIEVAL_RECORD,CONTEXT_SPECIFIC,false));
			//value = BerEncoder.encodeExternal();
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		//SurrogateDiagnostic
		if(record instanceof SurrogateDiagnostic){
			SurrogateDiagnostic surrDiag = (SurrogateDiagnostic)record;
			bs.write(AsnUtil.encodeTag(SURROGATE_DIAGNOSTIC,CONTEXT_SPECIFIC,false));
			value = encodeChoiceDiagRec(surrDiag.getSurrogateDiagnostic()); 
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
			
		}//if
		
		//Starting Fragment
		if(record instanceof StartingFragment){
			StartingFragment startingFrag = (StartingFragment)record;
			bs.write(AsnUtil.encodeTag(STARTING_FRAGMENT,CONTEXT_SPECIFIC,false));
			value = encodeChoiceFragmentSyntax(startingFrag.getStartingFragment());
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		//Intermediate Fragment
		if(record instanceof IntermediateFragment){
			IntermediateFragment intermediateFrag = (IntermediateFragment)record;
			bs.write(AsnUtil.encodeTag(INTERMEDIATE_FRAGMENT,CONTEXT_SPECIFIC,false));
			value = encodeChoiceFragmentSyntax(intermediateFrag.getIntermediateFragment());
			bs.write(value.length);
			bs.write(value);
		}//if
	
		//Final Fragment
		if(record instanceof FinalFragment){
			FinalFragment finalFrag = (FinalFragment)record;
			bs.write(AsnUtil.encodeTag(FINAL_FRAGMENT,CONTEXT_SPECIFIC,false));
			value = encodeChoiceFragmentSyntax(finalFrag.getFinalFragment());
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		return bs.toByteArray();
		
	}//encodeChoiceRecord
	
	
	public byte[] encodeChoiceFragmentSyntax(FragmentSyntax fragmentSyntax)
	throws Exception{
		byte[] value = null;
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		
		//ExternallyTagged
		if(fragmentSyntax instanceof ExternallyTagged){
			bs.write(AsnUtil.encodeTag(EXTERNAL,UNIVERSAL,false));
			//TODO: externallyTagged : External
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		if(fragmentSyntax instanceof NotExternallyTagged){
			bs.write(AsnUtil.encodeTag(OCTET_STRING,UNIVERSAL,false));
			value = BerEncoder.encodeOctetString(((NotExternallyTagged)fragmentSyntax).getNotExternallyTagged());
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		return bs.toByteArray();
	}//encodeChoiceFragmentSyntax
	
	
	public byte[] encodeChoiceDiagRec(DiagRec diagRec)
	throws Exception{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		
		//DefaultDiagFormat
		if(diagRec instanceof DefaultDiagFormat){
			bs.write(encodeSequenceDefaultDiagFormat((DefaultDiagFormat)diagRec));
		}//if
		
		if(diagRec instanceof ExternallyDefined){
			byte[] value = null;
			//TODO:ExternallyDefined :External
			bs.write(AsnUtil.encodeTag(EXTERNAL,UNIVERSAL,false));
			//value = BerEncoder.encodeExternal();
			bs.write(AsnUtil.encodeLength(value.length));
			bs.write(value);
		}//if
		
		return bs.toByteArray();
	}//encodeChoiceDiagRec
	
	
	public byte[] encodeSequenceDefaultDiagFormat(DefaultDiagFormat defDiagFormat)
	throws Exception{
		byte[] value = null;
		ByteArrayOutputStream result = new ByteArrayOutputStream();
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		
		//DiagnosticSetID
		bs.write(AsnUtil.encodeTag(OBJECT_IDENTIFIER,UNIVERSAL,false));
		value = BerEncoder.encodeObjectIdentifier(defDiagFormat.getDiagnosticSetId());
		bs.write(AsnUtil.encodeLength(value.length));
		bs.write(value);
		
		//condition
		bs.write(AsnUtil.encodeTag(INTEGER,UNIVERSAL,false));
		value = BerEncoder.encodeInteger(defDiagFormat.getCondition());
		bs.write(AsnUtil.encodeLength(value.length));
		bs.write(value);
		
		//addInfo
		decodeChoiceAddInfo(defDiagFormat.getAddInfo(),bs);
		
		result.write(AsnUtil.encodeTag(SEQUENCE,UNIVERSAL,false));
		result.write(AsnUtil.encodeLength(bs.size()));
		result.write(bs.toByteArray());
		
		return result.toByteArray();	
			
	}//encodeSequenceDefaultDiagFormat
	
	
	public void decodeChoiceAddInfo(AddInfo addInfo,ByteArrayOutputStream result)
	throws Exception{
		byte[] value = null;
		
		//V2AddInfo
		if(addInfo instanceof V2AddInfo){
			result.write(AsnUtil.encodeTag(VISIBLE_STRING,UNIVERSAL,false));
			value = BerEncoder.encodeVisibleString(((V2AddInfo)addInfo).getV2AddInfo());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);			
		}//if
		
		
		//TODO:replace visiblestring to internationalstring
		//V3AddInfo
		if(addInfo instanceof V3AddInfo){
			result.write(AsnUtil.encodeTag(VISIBLE_STRING,UNIVERSAL,false));
			value = BerEncoder.encodeVisibleString(((V3AddInfo)addInfo).getV3AddInfo());
			result.write(AsnUtil.encodeLength(value.length));
			result.write(value);			
		}//if
		
	}//decodeChoiceAddInfo
	
}//Z3950BerEncoder